#include "EPSG/Texture.h"

#ifdef _WIN32
#include "Windows.h"
#endif

#include "GL/gl.h"
#include "GL/glu.h"
#include "EPSG/Bitmap.h"
#include "EPSG/Material.h"

EPSG::Texture::Texture()
{
	setPlaneS( 1, 0, 0, 0 );
	setPlaneT( 0, 1, 0, 0 );
}

EPSG::Texture::Texture( const char* path )
{
	Texture();
	loadFile( path );
}

EPSG::Texture::~Texture()
{
	glDeleteTextures( 1, &_texID );
}

bool EPSG::Texture::load()
{
	if( _imageBuffer == NULL )
		return false;

	if( _texFunc == GL_MODULATE )
		EPSG::Material::whiteMaterial()->load();

	glPushAttrib( GL_TEXTURE_BIT );
	glEnable( _texType );
	glBindTexture( _texType, _texID );

	glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, _texFunc );

	if( _projectiveType == EPSG_PROJECTION_OBJECT )
	{
		glEnable( GL_TEXTURE_GEN_S );
		glEnable( GL_TEXTURE_GEN_T );

		glTexGeni(  GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR );
		glTexGenfv( GL_S, GL_EYE_PLANE, _planeS );

		glTexGeni(  GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR );
		glTexGenfv( GL_T, GL_EYE_PLANE, _planeT );
		return true;
	}
	else if( _projectiveType == EPSG_PROJECTION_EYE )
	{
		glEnable( GL_TEXTURE_GEN_S );
		glEnable( GL_TEXTURE_GEN_T );

		glMatrixMode( GL_MODELVIEW );
		glPushMatrix();
		glLoadIdentity();

		glTexGeni(  GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR );
		glTexGenfv( GL_S, GL_EYE_PLANE, _planeS );

		glTexGeni(  GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR );
		glTexGenfv( GL_T, GL_EYE_PLANE, _planeT );

		glPopMatrix();
		return true;
	}

	return false;
}

void EPSG::Texture::unload()
{
	glPopAttrib();
	
	if( _texFunc == GL_MODULATE )
		EPSG::Material::whiteMaterial()->unload();
}

bool EPSG::Texture::loadFile( std::string path,
							  TextureType type /*= EPSG_TEXTURE_2D*/,
							  TextureFunc func /*= EPSG_MODULATE*/,
							  TextureFilter minFilter /*= EPSG_LINEAR_MIPMAP_LINEAR*/,
							  TextureFilter magFilter /*= EPSG_LINEAR*/,
							  TextureRepeat repMode /*= EPSG_REPEAT */ )

{
	_texType = epsgToGL_TextureType( type );
	if( _texType == GL_INVALID_ENUM )
		return false;

	_texFunc = epsgToGL_TextureFunc( func );
	if( _texFunc == GL_INVALID_ENUM )
		return false;

	_minFilter = epsgToGL_TextureFilter( minFilter );
	if( _minFilter == GL_INVALID_ENUM )
		return false;

	_magFilter = epsgToGL_TextureFilter( magFilter );
	if( _magFilter == GL_INVALID_ENUM )
		return false;

	_repMode = epsgToGL_TextureRepeat( repMode );
	if( _repMode == GL_INVALID_ENUM )
		return false;

	BITMAPINFO* bmpInfo = new BITMAPINFO();
	_imageBuffer = (GLubyte*)LoadDIBitmap( path.data(), &bmpInfo );
	if( _imageBuffer == NULL )
	{
		printf( "BMP failure\n" );
		return false;
	}
	_loadedImagePath = path;
	printf( "BMP: %d x %d\n", bmpInfo->bmiHeader.biWidth, bmpInfo->bmiHeader.biHeight );

	glGenTextures( 1, &_texID );
	glBindTexture( _texType, _texID );

	glTexParameterf( _texType, GL_TEXTURE_MIN_FILTER, _minFilter );
	glTexParameterf( _texType, GL_TEXTURE_MAG_FILTER, _magFilter );

	glTexParameterf( _texType, GL_TEXTURE_WRAP_S, _repMode );
	glTexParameterf( _texType, GL_TEXTURE_WRAP_T, _repMode );

	if( _texType == GL_TEXTURE_1D )
	{
		gluBuild1DMipmaps( _texType, 3, bmpInfo->bmiHeader.biWidth,
						   GL_BGR_EXT, GL_UNSIGNED_BYTE, _imageBuffer );
	}
	else
	{
		gluBuild2DMipmaps( _texType, 3, bmpInfo->bmiHeader.biWidth, bmpInfo->bmiHeader.biHeight, 
						   GL_BGR_EXT, GL_UNSIGNED_BYTE, _imageBuffer );
	}

	return true;
}

void EPSG::Texture::setProjective( TextureProjective projectiveType )
{
	_projectiveType = projectiveType;
}

void EPSG::Texture::setPlaneS( float a, float b, float c, float d )
{
	_planeS[0] = a;
	_planeS[1] = b;
	_planeS[2] = c;
	_planeS[3] = d;
}

void EPSG::Texture::setPlaneT( float a, float b, float c, float d )
{
	_planeT[0] = a;
	_planeT[1] = b;
	_planeT[2] = c;
	_planeT[3] = d;
}

unsigned int EPSG::Texture::epsgToGL_TextureType( TextureType& type )
{
	switch( type )
	{
	case EPSG_TEXTURE_1D:
		return GL_TEXTURE_1D;

	case EPSG_TEXTURE_2D:
		return GL_TEXTURE_2D;

	//case EPSG_TEXTURE_3D:
	//	return GL_TEXTURE_3D;

	default:
		return GL_INVALID_ENUM;
	}
}

float EPSG::Texture::epsgToGL_TextureFunc( TextureFunc& func )
{
	switch( func )
	{
	case EPSG_MODULATE:
		return GL_MODULATE;

	case EPSG_DECAL:
		return GL_DECAL;

	case EPSG_BLEND:
		return GL_BLEND;

	case EPSG_REPLACE:
		return GL_REPLACE;

	default:
		return GL_INVALID_ENUM;
	}
}

float EPSG::Texture::epsgToGL_TextureFilter( TextureFilter& filter )
{
	switch( filter )
	{
	case EPSG_NEAREST:
		return GL_NEAREST;

	case EPSG_LINEAR:
		return GL_LINEAR;

	case EPSG_NEAREST_MIPMAP_NEAREST:
		return GL_NEAREST_MIPMAP_NEAREST;

	case EPSG_LINEAR_MIPMAP_NEAREST:
		return GL_LINEAR_MIPMAP_NEAREST;

	case EPSG_NEAREST_MIPMAP_LINEAR:
		return GL_NEAREST_MIPMAP_LINEAR;

	case EPSG_LINEAR_MIPMAP_LINEAR:
		return GL_LINEAR_MIPMAP_LINEAR;

	default:
		return GL_INVALID_ENUM;
	}
}

float EPSG::Texture::epsgToGL_TextureRepeat( TextureRepeat& repeat )
{
	switch( repeat )
	{
	case EPSG_REPEAT:
		return GL_REPEAT;

	case EPSG_CLAMP:
		return GL_CLAMP;

	//case EPSG_CLAMP_TO_BORDER:
	//	return GL_CLAMP_TO_BORDER;

	//case EPSG_CLAMP_TO_EDGE:
	//	return GL_CLAMP_TO_EDGE;

	default:
		return GL_INVALID_ENUM;
	}
}